﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Drawing;
using Tesla.Core;
using Tesla.Graphics;

namespace Tesla.Windows.Core {
    /// <summary>
    /// Concrete System.Windows.Forms implementation of a <see cref="Window"/>. This implementation has the option
    /// to create a single form that can be run using an IWindowHost or a control that can be added
    /// to an existing windows forms application (such as an editor).
    /// </summary>
    public sealed class SWFWindowImplementation : IWindowImplementation {
        private IRenderControl _control;
        private SwapChain _swapChain;
        private bool _isPrimaryWindow;
        private bool _isFullScreen;
        private bool _isDisposed;
        private int _preferredWidth;
        private int _preferredHeight;

        /// <summary>
        /// Event for when the window's client area is changed (e.g. during resize)
        /// </summary>
        public event EventHandler ClientSizeChanged;

        /// <summary>
        /// Event for when the window's orientation changed.
        /// </summary>
        public event EventHandler OrientationChanged;

        /// <summary>
        /// Event for when the window is the primary window and has been activated.
        /// </summary>
        public event EventHandler Activated;

        /// <summary>
        /// Event for when the window is the primary window and has been deactivated.
        /// </summary>
        public event EventHandler Deacativated;

        /// <summary>
        /// Event for when the window is the primary window and has been resumed.
        /// </summary>
        public event EventHandler Resume;

        /// <summary>
        /// Event for when the window is the primary window and has been suspended.
        /// </summary>
        public event EventHandler Suspend;

        /// <summary>
        /// Gets the underlying control that the window renders to.
        /// </summary>
        public IRenderControl RenderControl {
            get {
                return _control;
            }
        }

        /// <summary>
        /// Gets or sets if the user should be able to change the size of the window (e.g. drag the borders with the mouse).
        /// </summary>
        public bool AllowUserResizing {
            get {
                return _control.AllowUserResizing;
            }
            set {
                _control.AllowUserResizing = value;
            }
        }

        /// <summary>
        /// Gets if the window is primary. A primary window is defined to be a window that can be run by a window host, e.g. a Form.
        /// </summary>
        public bool IsPrimaryWindow {
            get {
                return _isPrimaryWindow;
            }
        }

        /// <summary>
        /// Gets the client bounds of the window.
        /// </summary>
        public Tesla.Math.Rectangle ClientBounds {
            get {
                if(_isFullScreen) {
                    return new Math.Rectangle(0, 0, _preferredWidth, _preferredHeight);
                }
                return _control.ClientBounds;
            }
        }

        /// <summary>
        /// Gets the handle to the underlying window.
        /// </summary>
        public IntPtr Handle {
            get {
                return _control.Handle;
            }
        }

        /// <summary>
        /// Gets if the window is minimized.
        /// </summary>
        public bool IsMinimized {
            get {
                return _control.IsMinimized;
            }
        }

        /// <summary>
        /// Gets or sets the window's title.
        /// </summary>
        public String Title {
            get {
                return _control.Text;
            }
            set {
                _control.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets if the mouse should be visible.
        /// </summary>
        public bool IsMouseVisible {
            get {
                return _control.IsMouseVisible;
            }
            set {
                _control.IsMouseVisible = value;
            }
        }

        /// <summary>
        /// Gets or sets if the window should be full screen. This toggles the effect.
        /// </summary>
        public bool IsFullScreen {
            get {
                return _isFullScreen;
            }
            set {
                if(!_isFullScreen && value == true) {
                    _isFullScreen = true;
                    _swapChain.ToggleFullScreen();
                } else if(_isFullScreen && value == false) {
                    _isFullScreen = false;
                    _swapChain.ToggleFullScreen();
                    //Make sure we're visible
                    ((System.Windows.Forms.Control) _control).Show();
                }
            }
        }

        /// <summary>
        /// Gets or sets the swap chain owned by the window.
        /// </summary>
        public SwapChain SwapChain {
            get {
                return _swapChain;
            }
        }

        /// <summary>
        /// Gets if the window has been disposed.
        /// </summary>
        public bool IsDisposed {
            get {
                return _isDisposed;
            }
        }

        /// <summary>
        /// Disposes of the window.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="SWFWindowImplementation"/> class.
        /// </summary>
        /// <param name="pp">The presentation parameters of the window</param>
        /// <param name="isPrimaryWindow">True if the window is primary, false otherwise. Primary windows can be run inside a window host. Secondary windows
        /// can be added to existing GUI applications.</param>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if there was an error creating the underlying control.</exception>
        public SWFWindowImplementation(PresentationParameters pp, bool isPrimaryWindow) {
            _preferredWidth = pp.BackBufferWidth;
            _preferredHeight = pp.BackBufferHeight;
            _isPrimaryWindow = isPrimaryWindow;
            _isDisposed = false;
            _isFullScreen = false;

            if(isPrimaryWindow) {
                RenderForm f = new RenderForm("Tesla Graphics Engine", pp.BackBufferWidth, pp.BackBufferHeight);
                f.Resume += new EventHandler(OnResume);
                f.Suspend += new EventHandler(OnSuspend);
                f.ApplicationActivated += new EventHandler(OnActivated);
                f.ApplicationDeactivated += new EventHandler(OnDeactivated);
                _control = f;
            } else {
                _control = new RenderControl("Tesla Graphics Engine", pp.BackBufferWidth, pp.BackBufferHeight);
            }

            _control.UserResized += new EventHandler(OnClientSizeChanged);

            bool fullScreen = pp.IsFullScreen;
            pp.IsFullScreen = false;
            _swapChain = new SwapChain(_control.Handle, pp);
            if(fullScreen) {
                IsFullScreen = fullScreen;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SWFWindowImplementation"/> class.
        /// </summary>
        /// <param name="pp">The presentation parameters of the window</param>
        /// <param name="title">The title of the window</param>
        /// <param name="isPrimaryWindow">True if the window is primary, false otherwise. Primary windows can be run inside a window host. Secondary windows
        /// can be added to existing GUI applications.</param>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if there was an error creating the underlying control.</exception>
        public SWFWindowImplementation(PresentationParameters pp, String title, bool isPrimaryWindow) {
            _preferredWidth = pp.BackBufferWidth;
            _preferredHeight = pp.BackBufferHeight;
            _isPrimaryWindow = isPrimaryWindow;
            _isDisposed = false;
            _isFullScreen = false;

            if(isPrimaryWindow) {
                RenderForm f = new RenderForm(title, pp.BackBufferWidth, pp.BackBufferHeight);
                f.Resume += new EventHandler(OnResume);
                f.Suspend += new EventHandler(OnSuspend);
                f.ApplicationActivated += new EventHandler(OnActivated);
                f.ApplicationDeactivated += new EventHandler(OnDeactivated);
                _control = f;
            } else {
                _control = new RenderControl(title, pp.BackBufferWidth, pp.BackBufferHeight);
            }
            _control.UserResized += new EventHandler(OnClientSizeChanged);

            bool fullScreen = pp.IsFullScreen;
            pp.IsFullScreen = false;
            _swapChain = new SwapChain(_control.Handle, pp);
            if(fullScreen) {
                IsFullScreen = fullScreen;
            }
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="SWFWindowImplementation"/> is reclaimed by garbage collection.
        /// </summary>
        ~SWFWindowImplementation() {
            Dispose(false);
        }

        /// <summary>
        /// Resizes the client area of the window using the specified parameters.
        /// </summary>
        /// <param name="width">Width of the client area</param>
        /// <param name="height">Height of the client area</param>
        public void Resize(int width, int height) {
            //If already in full screen, resize swap chain and notify any event handlers
            if(_isFullScreen) {
                _preferredWidth = width;
                _preferredHeight = height;
                _swapChain.Resize(width, height);
                if(ClientSizeChanged != null) {
                    ClientSizeChanged(this, EventArgs.Empty);
                }
            //Otherwise resize the control, which will cause a WM_Size message
            } else {
                System.Windows.Forms.Control control = _control as System.Windows.Forms.Control;
                if(control != null) {
                    control.ClientSize = new Size(width, height);
                    _preferredWidth = width;
                    _preferredHeight = height;
                    if(!_isFullScreen) {
                        System.Windows.Forms.Form f = control as System.Windows.Forms.Form;
                        if(f != null) {
                            System.Drawing.Rectangle rect = System.Windows.Forms.Screen.FromHandle(_control.Handle).Bounds;
                            System.Drawing.Point center = new System.Drawing.Point(rect.Width / 2, rect.Height / 2);
                            f.Location = new System.Drawing.Point(center.X - width / 2, center.Y - height / 2);
                        }
                    }
                }
            }
        }

        private void OnClientSizeChanged(Object sender, EventArgs args) {

            if(!IsMinimized && _swapChain != null) {

                //When in full screen, use the preferred width/height
                if(_isFullScreen) {
                    _swapChain.Resize(_preferredWidth, _preferredHeight);
                //Otherwise use the client bounds
                } else {
                    Tesla.Math.Rectangle rect = _control.ClientBounds;
                    int width = rect.Width;
                    int height = rect.Height;
                    _swapChain.Resize(rect.Width, rect.Height);
                }
            }

            if(ClientSizeChanged != null) {
                ClientSizeChanged(sender, args);
            }
        }

        private void OnOrientationChanged(Object sender, EventArgs args) {
            if(OrientationChanged != null) {
                OrientationChanged(sender, args);
            }
        }

        private void OnResume(Object sender, EventArgs args) {
            if(Resume != null) {
                Resume(sender, args);
            }
        }

        private void OnSuspend(Object sender, EventArgs args) {
            if(Suspend != null) {
                Suspend(sender, args);
            }
        }

        private void OnActivated(Object sender, EventArgs args) {
            if(Activated != null) {
                Activated(sender, args);
            }
        }

        private void OnDeactivated(Object sender, EventArgs args) {
            if(Deacativated != null) {
                Deacativated(sender, args);
            }
        }

        private void Dispose(bool disposing) {
            if(!_isDisposed) {
                if(disposing) {
                    if(_control != null) {
                        _control.Dispose();
                    }
                    if(_swapChain != null) {
                        _swapChain.Dispose();
                    }
                }
                _isDisposed = true;
            }
        }
    }
}
